Fix: CT parameter name uniqueness in CancellationToken overload generation#339
Conversation
…I params named 'cancellationToken' Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com> Agent-Logs-Url: https://github.com/fsprojects/SwaggerProvider/sessions/7d588ec7-c4df-4a6c-89f8-9c13c2472d29
ee4db30
into
repo-assist/feat-cancellationtoken-issue-212-2026-03-23-448c1cde90ec7d95
There was a problem hiding this comment.
Pull request overview
Fixes a codegen edge case in the v3 OpenAPI client type provider where injecting a CancellationToken overload could produce duplicate parameter names when the OpenAPI spec already defines a cancellationToken parameter.
Changes:
- Preserve the
usedNamesset from the OpenAPI parameter fold instead of discarding it. - Generate a unique injected CT parameter name (
cancellationToken,cancellationToken1,cancellationToken2, …) that avoids collisions with OpenAPI parameters. - Keep CancellationToken extraction position-based in
invokeCode(so behavior doesn’t depend on the chosen parameter name).
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Find a unique name for the CancellationToken parameter that doesn't | ||
| // conflict with any OpenAPI parameter already in use. | ||
| let ctParamName = | ||
| Seq.initInfinite(fun i -> | ||
| if i = 0 then | ||
| "cancellationToken" | ||
| else | ||
| $"cancellationToken{i}") | ||
| |> Seq.find(fun n -> not(Set.contains n usedNames)) | ||
|
|
||
| let ctParam = ProvidedParameter(ctParamName, typeof<Threading.CancellationToken>) | ||
| providedParameters @ [ ctParam ] |
There was a problem hiding this comment.
Add a regression test for the name-collision scenario this change fixes (an OpenAPI parameter named cancellationToken). Without a test that loads/generates a client from such a spec and asserts the generated overload compiles (and/or that the injected CT param gets a suffixed name), this could regress unnoticed.
| // conflict with any OpenAPI parameter already in use. | ||
| let ctParamName = | ||
| Seq.initInfinite(fun i -> | ||
| if i = 0 then | ||
| "cancellationToken" | ||
| else | ||
| $"cancellationToken{i}") | ||
| |> Seq.find(fun n -> not(Set.contains n usedNames)) | ||
|
|
There was a problem hiding this comment.
This introduces a second ad-hoc unique-name generator (Seq.initInfinite + Set.contains) even though the codebase already has UniqueNameGenerator with the same suffixing convention (and case-insensitive uniqueness). Consider factoring this into a shared helper (or reusing UniqueNameGenerator by seeding it with usedNames) to avoid future divergence in how “unique names” are defined.
| // conflict with any OpenAPI parameter already in use. | |
| let ctParamName = | |
| Seq.initInfinite(fun i -> | |
| if i = 0 then | |
| "cancellationToken" | |
| else | |
| $"cancellationToken{i}") | |
| |> Seq.find(fun n -> not(Set.contains n usedNames)) | |
| // conflict with any OpenAPI parameter already in use, using the shared | |
| // UniqueNameGenerator to keep suffixing and case sensitivity consistent. | |
| let nameGen = UniqueNameGenerator() | |
| usedNames |> Set.iter (fun n -> nameGen.Claim n |> ignore) | |
| let ctParamName = nameGen.Claim "cancellationToken" |
…dering + uniqueness improvements from this PR Both branches fixed CT name uniqueness independently. This branch additionally fixes the parameter ordering bug (CT must precede optional params). The merge keeps the more complete implementation (buildProvidedParameters helper, two-pass required/optional split, ctArgIndex insertion) and discards the simpler base-branch approach that still appended CT after optional params.
…vider generated methods (closes #212) (#336) * feat: add CancellationToken support to OpenApiClientProvider generated methods (closes #212) - Add CallAsync overload with CancellationToken to ProvidedApiClientBase - Thread CancellationToken from generated methods through to HttpClient.SendAsync - Each generated method gains an optional cancellationToken parameter (defaults to CancellationToken.None) - Backward-compatible: existing call sites without CT continue to work unchanged - Add unit tests: success with CancellationToken.None, cancellation propagation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * ci: trigger checks * fix: replace optional struct CancellationToken parameter with method overloads (#337) * Initial plan * Fix: revert global.json and address CancellationToken build failures Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com> Agent-Logs-Url: https://github.com/fsprojects/SwaggerProvider/sessions/1861c3cb-6a0a-438a-aa31-f65b8c809f88 * fix: use method overloading for CancellationToken support instead of optional struct parameter Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com> Agent-Logs-Url: https://github.com/fsprojects/SwaggerProvider/sessions/1861c3cb-6a0a-438a-aa31-f65b8c809f88 --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com> * Add type provider integration tests for CancellationToken-overloaded methods (#338) * Fix: CT parameter name uniqueness in CancellationToken overload generation (#339) * Initial plan * fix: generate unique CT parameter name to avoid collision with OpenAPI params named 'cancellationToken' Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com> Agent-Logs-Url: https://github.com/fsprojects/SwaggerProvider/sessions/7d588ec7-c4df-4a6c-89f8-9c13c2472d29 --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com> * Fix CancellationToken parameter ordering and name collision in v3 OperationCompiler (#341) * Initial plan * fix: insert CT between required and optional params; generate unique CT name Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com> Agent-Logs-Url: https://github.com/fsprojects/SwaggerProvider/sessions/b0c519de-0186-40ca-8174-42ed67a5316a * fix: add explicit restore + --no-restore to BuildTests to fix NETSDK1005 Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com> Agent-Logs-Url: https://github.com/fsprojects/SwaggerProvider/sessions/565d6633-576d-4587-b924-a29b0ea53c2c --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com> * refactor: use UniqueNameGenerator for CT param name uniqueness Replace hand-coded recursive findUniqueName function with the existing UniqueNameGenerator utility (already used in DefinitionCompiler and for method name deduplication in OperationCompiler). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: add optional occupiedNames parameter to UniqueNameGenerator constructor Allows callers to pre-seed the generator with names that are already taken, so MakeUnique will never return any of those names without a numeric suffix. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * refactor: single CallAsync overload + single generated method with optional CancellationToken - Remove no-CT CallAsync overload from ProvidedApiClientBase; keep only the version with explicit CancellationToken (quotation code always supplies it) - Remove double-compilation in OperationCompiler: one method per operation with optional cancellationToken (null default = default(CancellationToken).None) - Update RuntimeHelpersTests to pass CancellationToken.None explicitly Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * cleanup: simplify OperationCompiler and add default/async CT tests - Use List.map instead of List.collect since compileOperation returns a single method - Clean up comments in OperationCompiler - Add test for calling generated method without CancellationToken (default token) - Add test for async (PreferAsync=true) generated method without CancellationToken * feat: propagate CancellationToken through ReadAsStringAsync/ReadAsStreamAsync via RuntimeHelpers Add readContentAsString and readContentAsStream wrappers to RuntimeHelpers with #if NET5_0_OR_GREATER guards, enabling CancellationToken propagation in generated quotation code that must compile against netstandard2.0. Also add explicit CancellationToken integration tests and conditional CT support in ProvidedApiClientBase error path. * test: add CT coverage for stream, text/plain, async cancellation, and async POST paths * fix: async tests --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: sergey-tihon <1197905+sergey-tihon@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Sergey Tihon <sergey.tihon@gmail.com>
If an OpenAPI spec defines a parameter named
cancellationToken, the generated overload would emit two parameters with the same name — one from the spec and one for the injectedCancellationToken.Changes
usedNamesset alongsideprovidedParametersinstead of discarding it with|> sndcancellationToken→cancellationToken1→cancellationToken2→ …, matching the existingUniqueNameGeneratorconventioninvokeCodealready extracts the CT argument by position (last inargs), so it correctly handles the CT regardless of the name chosen✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.